package org.ramoths.netcom.outils;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;

import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.Signer;
import org.bouncycastle.crypto.digests.SHA512Digest;
import org.bouncycastle.crypto.encodings.OAEPEncoding;
import org.bouncycastle.crypto.engines.RSAEngine;
import org.bouncycastle.crypto.generators.RSAKeyPairGenerator;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.crypto.params.RSAKeyGenerationParameters;
import org.bouncycastle.crypto.params.RSAKeyParameters;
import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters;
import org.bouncycastle.crypto.signers.RSADigestSigner;

public class Outils
{
	private static final SecureRandom srand;
	private static final RSAPrivateCrtKeyParameters clePriveeCrypt;
	private static final RSAKeyParameters clePubliqueCrypt;
	private static final RSAPrivateCrtKeyParameters clePriveeSign;
	private static final RSAKeyParameters clePubliqueSign;
	static
	{
		srand = new SecureRandom();
		RSAKeyGenerationParameters param = new RSAKeyGenerationParameters(
				BigInteger.valueOf(65537),
				srand,
				2048,
				500);
		RSAKeyPairGenerator generateur = new RSAKeyPairGenerator();
		generateur.init(param);
		AsymmetricCipherKeyPair cles = null;
		//
		cles = generateur.generateKeyPair();
		clePriveeCrypt = (RSAPrivateCrtKeyParameters) cles.getPrivate();
		clePubliqueCrypt = (RSAKeyParameters) cles.getPublic();
		//
		cles = generateur.generateKeyPair();
		clePriveeSign = (RSAPrivateCrtKeyParameters) cles.getPrivate();
		clePubliqueSign = (RSAKeyParameters) cles.getPublic();
	}
	
	public static RSAKeyParameters getClePubliqueCrypt()
	{
		return clePubliqueCrypt;
	}
	
	public static RSAKeyParameters getClePubliqueSign()
	{
		return clePubliqueSign;
	}
	
	private static final OAEPEncoding cryptageRSA = new OAEPEncoding(new RSAEngine());
	
	public static byte[] crypterRSA(byte[] msg, int debut, int taille, RSAKeyParameters clePublique)
	{
		synchronized (cryptageRSA)
		{
			cryptageRSA.init(true, new ParametersWithRandom(clePublique, srand));
			int iterations = 1 + (taille-1) / cryptageRSA.getInputBlockSize();
			byte[] r = new byte[iterations * cryptageRSA.getOutputBlockSize()];
			//
			synchronized (cryptageRSA)
			{
				for (int i=0; i<iterations; i++)
				{
					try
					{
						System.arraycopy(
								cryptageRSA.processBlock(
										msg,
										debut + i * cryptageRSA.getInputBlockSize(),
										(i < iterations-1) ? cryptageRSA.getInputBlockSize() : taille % cryptageRSA.getInputBlockSize()
										),
								0,
								r,
								i * cryptageRSA.getOutputBlockSize(),
								cryptageRSA.getOutputBlockSize());
					}
					catch (Exception ex)
					{
						ex.printStackTrace();
					}
				}
			}
			return r;
		}
	}
	
	public static byte[] crypterRSA(byte[] msg, RSAKeyParameters clePublique)
	{
		return crypterRSA(msg, 0, msg.length, clePublique);
	}
	
	private static final OAEPEncoding decryptageRSA = new OAEPEncoding(new RSAEngine());
	static
	{
		decryptageRSA.init(false, new ParametersWithRandom(clePriveeCrypt, srand));
	}
	
	public static byte[] decrypterRSA(byte[] msg, int debut, int taille)
	{
		synchronized (decryptageRSA)
		{
			int iterations = 1 + (taille-1) / decryptageRSA.getInputBlockSize();
			byte[] r = new byte[iterations * decryptageRSA.getOutputBlockSize()];
			byte[] tmp = null;
			//
			for (int i=0; i<iterations; i++)
			{
				try
				{
					tmp = decryptageRSA.processBlock(
							msg,
							debut + i * decryptageRSA.getInputBlockSize(),
							decryptageRSA.getInputBlockSize()
							);
					System.arraycopy(
							tmp,
							0,
							r,
							i * decryptageRSA.getOutputBlockSize(),
							(i<iterations-1) ? decryptageRSA.getOutputBlockSize() : tmp.length
							);
				}
				catch (Exception ex)
				{
					ex.printStackTrace();
				}
			}
			byte[] tmp2 = new byte[(iterations-1) * decryptageRSA.getOutputBlockSize() + tmp.length];
			System.arraycopy(r, 0, tmp2, 0, tmp2.length);
			r = tmp2;
			return r;
		}
	}
	
	public static byte[] decrypterRSA(byte[] msg)
	{
		return decrypterRSA(msg, 0, msg.length);
	}
	
	private static final List<CipherAES> ciphersAES = new ArrayList<CipherAES>();
	private static final int NB_CIPHERS_AES = 50;
	static
	{
		for (int i=0; i<NB_CIPHERS_AES; i++)
			ciphersAES.add(new CipherAES(srand));
	}
	
	public static byte[] crypterAES(byte[] msg, int debut, int taille, byte[] cle, long sequenceIV)
	{
		CipherAES aes = null;
		synchronized (ciphersAES)
		{
			while (true)
			{
				for (CipherAES c : ciphersAES)
					if (!c.isOccupe())
					{
						aes = c;
						aes.setOccupe(true);
						break;
					}
				if (aes != null)
					break;
				try { ciphersAES.wait(); } catch (InterruptedException ex) {}
			}
		}
		try
		{
			return aes.crypter(msg, debut, taille, cle, sequenceIV);
		}
		finally
		{
			synchronized (ciphersAES)
			{
				aes.setOccupe(false);
				ciphersAES.notify();
			}
		}
	}
	
	public static byte[] crypterAES(byte[] msg, byte[] cle, long sequenceIV)
	{
		return crypterAES(msg, 0, msg.length, cle, sequenceIV);
	}
	
	public static byte[] decrypterAES(byte[] msg, int debut, int taille, byte[] cle, long sequenceIV)
	{
		CipherAES aes = null;
		synchronized (ciphersAES)
		{
			while (true)
			{
				for (CipherAES c : ciphersAES)
					if (!c.isOccupe())
					{
						aes = c;
						aes.setOccupe(true);
						break;
					}
				if (aes != null)
					break;
				try { ciphersAES.wait(); } catch (InterruptedException ex) {}
			}
		}
		try
		{
			return aes.decrypter(msg, debut, taille, cle, sequenceIV);
		}
		finally
		{
			synchronized (ciphersAES)
			{
				aes.setOccupe(false);
				ciphersAES.notify();
			}
		}
	}
	
	public static byte[] decrypterAES(byte[] msg, byte[] cle, long sequenceIV)
	{
		return decrypterAES(msg, 0, msg.length, cle, sequenceIV);
	}
	
	public static byte[] desLong(long valeur)
	{
		byte[] r = new byte[8];
		for (int i=0; i<8; i++)
		{
			r[i] = (byte) (valeur & 0x00000000000000FF);
			valeur = valeur >> 8;
		}
		return r;
	}
	
	public static long assLong(byte[] t, int indexe)
	{
		long r = 0;
		for (int i=0; i<8; i++)
		{
			r = (((long) t[7-i + indexe]) & 0x00000000000000FF) | r;
			if (i < 7)
				r = r << 8;
		}
		return r;
	}
	
	public static byte[] desInt(int valeur)
	{
		byte[] r = new byte[4];
		for (int i=0; i<4; i++)
		{
			r[i] = (byte) (valeur & 0x000000FF);
			valeur = valeur >> 8;
		}
		return r;
	}
	
	public static int assInt(byte[] t, int indexe)
	{
		int r = 0;
		for (int i=0; i<4; i++)
		{
			r = (((int) t[3-i+indexe]) & 0x000000FF) | r;
			if (i < 3)
				r = r << 8;
		}
		return r;
	}
	
	private static final Signer signatureRSA = new RSADigestSigner(new SHA512Digest());
	static
	{
		signatureRSA.init(true, new ParametersWithRandom(clePriveeSign, srand));
	}
	
	public static byte[] signerRSA(byte[] msg, int debut, int taille)
	{
		synchronized (signatureRSA)
		{
			try
			{
				signatureRSA.reset();
				signatureRSA.update(msg, debut, taille);
				return signatureRSA.generateSignature();
			}
			catch (Exception ex)
			{
				ex.printStackTrace();
				return null;
			}
		}
	}
	
	public static byte[] signerRSA(byte[] msg)
	{
		return signerRSA(msg, 0, msg.length);
	}
	
	private static final Signer verificationSignatureRSA = new RSADigestSigner(new SHA512Digest());
	
	public static boolean verifierSignatureRSA(byte[] msg, int debut, int taille, byte[] signature, RSAKeyParameters clePublique)
	{
		synchronized (verificationSignatureRSA)
		{
			verificationSignatureRSA.init(false, new ParametersWithRandom(clePublique, srand));
			verificationSignatureRSA.reset();
			verificationSignatureRSA.update(msg, debut, taille);
			return verificationSignatureRSA.verifySignature(signature);
		}
	}
	
	public static boolean verifierSignatureRSA(byte[] msg, byte[] signature, RSAKeyParameters clePublique)
	{
		return verifierSignatureRSA(msg, 0, msg.length, signature, clePublique);
	}
	
	public static SecureRandom getSRand()
	{
		return srand;
	}
	
	public static byte[] lectureFlux(InputStream is, byte[] tempon, int debut, int taille) throws IOException, FinFluxException
	{
		int nb = 0;
		while (nb < taille)
		{
			nb += is.read(tempon, debut+nb, taille-nb);
			if (nb == -1)
				throw  FinFluxException.INSTANCE;
		}
		return tempon;
	}
	
	public static byte[] lectureFlux(InputStream is, int taille) throws IOException, FinFluxException
	{
		byte[] r = new byte[taille];
		return lectureFlux(is, r, 0, r.length);
	}
	
	public static byte[] lectureFlux(InputStream is, byte[] tempon) throws IOException, FinFluxException
	{
		return lectureFlux(is, tempon, 0, tempon.length);
	}
	
	public static void copierFlux(InputStream source, OutputStream destination) throws Exception
	{
		byte[] tempon = new byte[1024 * 10];
		int nb = 0;
		try
		{
			while (true)
			{
				nb = source.read(tempon);
				if (nb == -1)
					break;
				destination.write(tempon, 0, nb);
			}
		}
		catch (Exception ex)
		{
			throw new Exception("Erreur de copie de flux!", ex);
		}
	}
	
	public static void copierFichier(File source, File destination) throws Exception
	{
		FileInputStream fis = null;
		FileOutputStream fos = null;
		try
		{
			fis = new FileInputStream(source);
			fos = new FileOutputStream(destination);
			fos.getChannel().transferFrom(fis.getChannel(), 0, Long.MAX_VALUE);
			fos.close();
			try { fis.close(); } catch (Exception exx) {}
		}
		catch (Exception ex)
		{
			if (fis != null)
				try { fis.close(); } catch (Exception exx) {}
			if (fos != null)
				try { fos.close(); } catch (Exception exx) {}
			throw new Exception("Erreur de copie de fichier!", ex);
		}
	}
}
